package com.digidata.rest.testing.engine

import java.text.BreakDictionary
import java.util.LinkedList
import java.util.List

import junit.framework.Test
import junit.framework.TestResult
import junit.framework.TestSuite

import org.codehaus.groovy.tools.*
import groovy.io.*
import groovy.util.GroovyScriptEngine
import groovy.util.GroovyTestCase
import groovy.util.GroovyTestSuite

import com.digidata.util.Log


/**
 * The TestRunner class provides the necessary functions to load all know tests in a provided location
 * and execute tests as provided.
 * @author geoff.martin
 *
 */
class TestRunner {
	TestSuite customSuite = new TestSuite()
	
	/**
	 * Stores the result of the test(s)
	 */
	public TestResult testResult
	/**
	 * A map of the short class names as Strings to the class
	 */
	private Map <String, Class> classMap = [:]
		
	/**
	 * List of the test suites loaded
	 */
	private List<TestSuite> suiteList = []
	
	private GroovyScriptEngine gse = new GroovyScriptEngine()
	private GroovyClassLoader loader = new GroovyClassLoader(gse.getGroovyClassLoader())
	
	/**
	 * This constructor for the TestRunner class will load the test files in the path provided, creates
	 * GroovyTestSuites for each it will then search the suites for the name of the test that matches 
	 * the testName and runs that test.  The result is stored in the testResult property.
	 * @param pathToTestFile
	 * @param testName Name of the test to run.  Name must either be: the unique number of the test, 
	 * the unique name of the test, the number + the name, or in the form 'test_<testnumber>_<testname>'
	 */
	public TestRunner(String pathToTestFile, String testName){
		List <String> testNameList = [testName] 
		testResult = runTest(pathToTestFile, testNameList)
	}
	
	
	/**
	* This constructor for the TestRunner class will load the test files in the path provided, creates
	* GroovyTestSuites for each it will then search the suites for the name of the test(s) that matches
	* in the testNameList and runs those tests.  The result is stored in the testResult property.
	* @param pathToTestFile
	* @param testNameList List<String> Names of the tests to run.  Name must either be: the unique number of the test,
	* the unique name of the test, the number + the name, or in the form 'test_<testnumber>_<testname>'
	*/
	public TestRunner(String pathToTestFile, List<String> testNameList){
		testResult = runTest(pathToTestFile, testNameList)
	}
	
	public TestRunner(String pathToTestDirectory){
		//create the test runner, but do not execute
		//test runner should load tests from the directory
		//loadTests()
		//loadTests(pathToTestDirectory, loader)
		loader = loadTests(pathToTestDirectory, loader)
	}
	
	//New method added by Josh
	public addTests(def testName){
		Test test
		try{
			testName.each{
				println it
				//test = getTest(testName)
				test = getTest(it)
				//testList.add(test)
				customSuite.addTest(test)
			}
		}
		catch(ArrayIndexOutOfBoundsException e){
			println "Cannot find requested test: $testName"
			System.exit(1)
		}
	}
	
	/**
	 * Use find and run the test(s).
	 * @param pathToTestFile Location of the test suite files
	 * @param testName Name of the test to run
	 * @return Junit TestResult
	 */
	private TestResult runTest(String pathToTestFile, List<String> testName){
		
		loader = loadTests(pathToTestFile, loader)
		
		addTests()
		
		TestResult result = new TestResult()
		//test.run(result)
		customSuite.run(result)
		return result
	}
	
	//New override method added by Josh
	public runTest(TestResult result){
		customSuite.run(result)
	}
	
	/**
	 * Get the Junit test for the testName provided
	 * @param testName Name of the test to locate.
	 * @return Junit.Test
	 */
	private Test getTest(String testName){
		
		if(testName == "" || testName == null || testName == "test" || testName == "_")
			throw new Exception("Test name provided was either: \"\", \"test\", \"_\", or null.")

		Test test = null 
		suiteList.each{
			def index = it.tests().findIndexOf{it.name == testName}
			if(index != -1){
				//println "found it by full name!______________$it.name"
				test = it.testAt(index)
				return
			}
			else{
				index = it.tests().collect{it.name.split(/_/)[1]}.findIndexOf{it == testName}
				if(index != -1){
					//println "found it by number!______________$it.name"
					test = it.testAt(index)
					return
				}
				else{
					index = it.tests().collect{it.name.split(/_/)[2]}.findIndexOf{it == testName}
					if(index != -1){
						//println "found it by short name!______________$it.name"
						test = it.testAt(index)
						return
					}
					else{
						index = it.tests().collect{it.name.replace('test_', '')}.findIndexOf{it == testName}
						if(index != -1){
							//println "found it by short number + name!______________$it.name"
							test = it.testAt(index)
							return
						}
					}
				}
			}
		}
		
		if(test == null)
			throw new Exception("Failed to find test: $testName")
		return test
	}
	
	/**
	 * Break up the test name to get the test number
	 * @param testName full name of the test
	 * @return the string representation of the numeric portion of the full test name
	 */
	private def getTestNumber(String testName){
		return testName.split(/_/)[1]
	}
	
	/**
	 * load the tests in the given path 
	 * @param rootDir Root directory to traverse from
	 * @param loader The root class loader
	 * @return the loader
	 */
	public GroovyClassLoader loadTests(String rootDir, GroovyClassLoader loader){
		
		File root = new File(rootDir)
		
		root.eachFileRecurse(FileType.FILES, {
			//skip any SVN meta-data
			if(!it.getParent().contains('.svn')){
				try{
					classMap.putAt(it.name.replace('.groovy', ''), loader.parseClass(it))
				}
				catch(Exception e){
					println "Error loading test files.  This is a known issue when non-code files are in the test path.  Please make sure that the test path only contains valid code files and try again."
					Log.info("Error loading test files.  This is a known issue when non-code files are in the test path.  Please make sure that the test path only contains valid code files and try again.")
					Log.info(e.message)
					throw new Exception("Error loading test files.  This is a known issue when non-code files are in the test path.  Please make sure that the test path only contains valid code files and try again.", e)
				}
			}
		})
		
		classMap.each{
			suiteList.add(new TestSuite(it.value))
		}

		return loader
	}
}
